#include "particle_system.hh"
#include "gl.hh"
#include "glu.hh"
#include "../log.hh"
#include <algorithm>
#include <stdexcept>
#include <glm/vec4.hpp>
#include <cmath>

static float rand11()
{
	return 2 * (rand() & 0xFFFF) / (float)0xFFFF - 1;
}

static float lirp(float x, float xf, float xt, float df, float dt)
{
	return (x-xf) / (xt-xf) * (dt-df) + df;
}

static gl::uint_t dot_texture()
{
	static gl::uint_t tex=0;
	if (!tex) {
		gl::pixel_storei(gl::_unpack_alignment, 1);
		gl::pixel_storei(gl::_pack_alignment, 1);

		gl::gen_textures(1, &tex);

		gl::ubyte_t img[64*64];
		for (int y=0; y<64; y++)
			for (int x=0; x<64; x++) {
				int u = (x-32);
				int v = (y-32);
				int l = sqrt(u*u + v*v) * 0xFF / 32;
				img[y*64 + x] = (l < 0xFF ? 0xFF - l : 0);
			}

		gl::bind_texture(gl::_texture_2d, tex);
		gl::tex_image_2d(gl::_texture_2d, 0,
				gl::_alpha8, 64, 64, 0,
				gl::_alpha, gl::_unsigned_byte, img);
		gl::tex_parameteri(gl::_texture_2d, gl::_texture_min_filter, gl::_linear);
		gl::tex_parameteri(gl::_texture_2d, gl::_texture_mag_filter, gl::_linear);
		gl::tex_parameteri(gl::_texture_2d, gl::_texture_wrap_s, gl::_clamp_to_edge);
		gl::tex_parameteri(gl::_texture_2d, gl::_texture_wrap_t, gl::_clamp_to_edge);
		gl::tex_envi(gl::_texture_env, gl::_texture_env_mode, gl::_modulate);

		auto err = gl::get_error();
		if (err != gl::_no_error)
			throw std::runtime_error{(char const*)glu::error_string(err)};
	}
	return tex;
}

namespace gl
{
	void particle_system::add(int life,
				glm::vec3 const& pos,
				glm::vec3 const& v,
				glm::vec3 const& a,
				glm::vec3 const& color)
	{
		glm::vec3 r = {
			randomness.x * rand11(),
			randomness.y * rand11(),
			randomness.z * rand11() };
		particles.push_back({life, pos, v+r*0.01f, a+r, color});
	}

	void particle_system::update(float step)
	{
		auto it = std::remove_if(
				particles.begin(),
				particles.end(),
				[step](value_type& p) {
					p.update(step);
					return p.expired();
				});
		particles.erase(it, particles.end());

		// TODO: use a sorted container. multiset?
		std::sort(
				particles.begin(),
				particles.end(),
				[](particle const& a, particle const& b) {
					return (a.position.z < b.position.z);
				});
	}

	void particle_system::render(bool spark) const
	{
		// FIXME: move blend_func thing to gl::context
		gl::blend_func(gl::_src_alpha, gl::_one);
		gl::bind_texture(gl::_texture_2d, dot_texture());
		gl::enable(gl::_texture_2d);
		if (spark) {
			gl::point_size(10);
			gl::begin(gl::_quads);
			for (auto& p: particles) {
				float a = float(p.age) / p.life;
				float ca;
				if (a < 0.4) ca = lirp(a, 0, 0.4, 1, 0.2);
				else ca = lirp(a, 0.4, 1, 0.2, 0);
				glm::vec4 c{p.color, ca};
				gl::color4fv(&c[0]);

				// b = bottom, t = top, l = left, r = right
				float size;
				if (a < 0.4) size = lirp(a, 0, 0.1, 0.1, 0.2);
				else size = lirp(a, 0.4, 1, 0.2, 1.2);
				glm::vec3 dim{size, size, 0};
				auto bl = p.position - dim;
				auto tr = p.position + dim;
				glm::vec3 tl{bl.x, tr.y, p.position.z};
				glm::vec3 br{tr.x, bl.y, p.position.z};
				//static glm::vec2 tex_bl
				gl::tex_coord2f(0, 0);
				gl::vertex3fv(&bl[0]);
				gl::tex_coord2f(1, 0);
				gl::vertex3fv(&br[0]);
				gl::tex_coord2f(1, 1);
				gl::vertex3fv(&tr[0]);
				gl::tex_coord2f(0, 1);
				gl::vertex3fv(&tl[0]);
			}
			gl::end();
		}
		gl::disable(gl::_texture_2d);
		gl::blend_func(gl::_src_alpha, gl::_one_minus_src_alpha);
	}
}

